# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2025 NV Access Limited, Babbage B.V.
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

"""
Functions that wrap Windows API functions from user32.dll.

When working on this file, consider moving to winAPI.
"""

import contextlib
import ctypes
from ctypes import (
	byref,
	WinError,
	Structure,
	c_long,
	c_short,
	c_uint,
	c_wchar,
	create_unicode_buffer,
	sizeof,
	wstring_at,
)
from ctypes.wintypes import (
	BOOL,
	HWND,
	POINT,
	RECT,
	DWORD,
	WCHAR,
)

import winBindings.user32
from winBindings import user32 as _user32
from winBindings.user32 import (
	WNDENUMPROC as _WNDENUMPROC,
	PAINTSTRUCT as _PAINTSTRUCT,
	GUITHREADINFO as _GUITHREADINFO,
	INPUT,
)
import winKernel
from collections.abc import Callable
from textUtils import WCHAR_ENCODING
import enum
from utils import _deprecate


__getattr__ = _deprecate.handleDeprecations(
	_deprecate.MovedSymbol(
		"WNDCLASSEXW",
		"winBindings.user32",
	),
	_deprecate.MovedSymbol(
		"WNDPROC",
		"winBindings.user32",
	),
	_deprecate.MovedSymbol(
		"PAINTSTRUCT",
		"winBindings.user32",
	),
	_deprecate.MovedSymbol(
		"SM_CXSCREEN",
		"winAPI.winUser.constants",
		"SystemMetrics",
		"CX_SCREEN",
	),
	_deprecate.MovedSymbol(
		"SM_CYSCREEN",
		"winAPI.winUser.constants",
		"SystemMetrics",
		"CY_SCREEN",
	),
	_deprecate.MovedSymbol(
		"SM_SWAPBUTTON",
		"winAPI.winUser.constants",
		"SystemMetrics",
		"SWAP_BUTTON",
	),
	_deprecate.MovedSymbol(
		"SM_XVIRTUALSCREEN",
		"winAPI.winUser.constants",
		"SystemMetrics",
		"X_VIRTUAL_SCREEN",
	),
	_deprecate.MovedSymbol(
		"SM_YVIRTUALSCREEN",
		"winAPI.winUser.constants",
		"SystemMetrics",
		"Y_VIRTUAL_SCREEN",
	),
	_deprecate.MovedSymbol(
		"SM_CXVIRTUALSCREEN",
		"winAPI.winUser.constants",
		"SystemMetrics",
		"CX_VIRTUAL_SCREEN",
	),
	_deprecate.MovedSymbol(
		"SM_CYVIRTUALSCREEN",
		"winAPI.winUser.constants",
		"SystemMetrics",
		"CY_VIRTUAL_SCREEN",
	),
	_deprecate.MovedSymbol(
		"HWINEVENTHOOK",
		"winBindings.user32",
	),
	_deprecate.MovedSymbol(
		"WINEVENTPROC",
		"winBindings.user32",
	),
	_deprecate.MovedSymbol("user32", "winBindings.user32", "dll"),
	_deprecate.MovedSymbol("GUITHREADINFO", "winBindings.user32"),
	_deprecate.MovedSymbol("Input", "winBindings.user32", "INPUT"),
	_deprecate.MovedSymbol("KeyBdInput", "winBindings.user32", "KEYBDINPUT"),
	_deprecate.MovedSymbol("HardwareInput", "winBindings.user32", "HARDWAREINPUT"),
	_deprecate.MovedSymbol("MouseInput", "winBindings.user32", "MOUSEINPUT"),
	_deprecate.MovedSymbol("INPUT_MOUSE", "winBindings.user32", "INPUT_TYPE", "MOUSE"),
	_deprecate.MovedSymbol("INPUT_KEYBOARD", "winBindings.user32", "INPUT_TYPE", "KEYBOARD"),
	_deprecate.MovedSymbol("KEYEVENTF_KEYUP", "winBindings.user32", "KEYEVENTF", "KEYUP"),
	_deprecate.MovedSymbol("KEYEVENTF_UNICODE", "winBindings.user32", "KEYEVENTF", "UNICODE"),
)
"""Module __getattr__ to handle backward compatibility."""

# rather than using the ctypes.c_void_p type, which may encourage attempting to dereference
# what may be an invalid or illegal pointer, we'll treat it as an opaque value.
HWNDVal = int

LRESULT = c_long
HCURSOR = c_long

# Standard window class stuff
#: Redraws the entire window if a movement or size adjustment changes the width of the client area.
CS_HREDRAW = 0x0002
#: Redraws the entire window if a movement or size adjustment changes the height of the client area.
CS_VREDRAW = 0x0001


class NMHdrStruct(Structure):
	_fields_ = [
		("hwndFrom", HWND),
		("idFrom", c_uint),
		("code", c_uint),
	]


# constants
ERROR_OLD_WIN_VERSION = 1150
MOUSEEVENTF_MOVE = 0x0001  # Movement occurred.
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
MOUSEEVENTF_RIGHTDOWN = 0x0008
MOUSEEVENTF_RIGHTUP = 0x0010
MOUSEEVENTF_MIDDLEDOWN = 0x0020
MOUSEEVENTF_MIDDLEUP = 0x0040
MOUSEEVENTF_XDOWN = 0x0080
MOUSEEVENTF_XUP = 0x0100
MOUSEEVENTF_WHEEL = 0x0800  # The wheel button is rotated.
MOUSEEVENTF_HWHEEL = 0x1000  # The wheel button is tilted.
WHEEL_DELTA = 120  # One wheel click. Negate to scroll down.
MOUSEEVENTF_ABSOLUTE = 0x8000  # The dx and dy parameters contain normalized absolute coordinates (0..65535).
GUI_CARETBLINKING = 0x00000001
GUI_INMOVESIZE = 0x00000002
GUI_INMENUMODE = 0x00000004
GUI_SYSTEMMENUMODE = 0x00000008
GUI_POPUPMENUMODE = 0x00000010
SPI_GETSTICKYKEYS = 0x003A
SPI_GETSCREENREADER = 70
SPI_SETSCREENREADER = 71
SPIF_UPDATEINIFILE = 1
SPIF_SENDCHANGE = 2
WS_DISABLED = 0x8000000
WS_VISIBLE = 0x10000000
WS_POPUP = 0x80000000
WS_GROUP = 0x20000
WS_THICKFRAME = 0x40000
WS_SIZEBOX = WS_THICKFRAME
WS_SYSMENU = 0x80000
WS_HSCROLL = 0x100000
WS_VSCROLL = 0x200000
WS_CAPTION = 0xC00000
WS_CLIPCHILDREN = 0x02000000
WS_EX_TOPMOST = 0x00000008
WS_EX_LAYERED = 0x80000
WS_EX_TOOLWINDOW = 0x00000080
WS_EX_TRANSPARENT = 0x00000020
WS_EX_APPWINDOW = 0x00040000
WS_EX_NOACTIVATE = 0x08000000
BS_GROUPBOX = 7
ES_MULTILINE = 4
LBS_OWNERDRAWFIXED = 0x0010
LBS_OWNERDRAWVARIABLE = 0x0020
LBS_HASSTRINGS = 0x0040
LBS_EXTENDEDSEL = 0x0800
CBS_OWNERDRAWFIXED = 0x0010
CBS_OWNERDRAWVARIABLE = 0x0020
CBS_HASSTRINGS = 0x00200
# PeekMessage
PM_REMOVE = 1
PM_NOYIELD = 2
# sendMessageTimeout
SMTO_ABORTIFHUNG = 0x0002
# getAncestor
GA_PARENT = 1
GA_ROOT = 2
GA_ROOTOWNER = 3
# getWindowLong
GWL_ID = -12
GWL_STYLE = -16
GWL_EXSTYLE = -20

# getWindow relationship parameters: TODO turn to enum
GW_HWNDFIRST = 0
GW_HWNDLAST = 1
GW_HWNDNEXT = 2
GW_HWNDPREV = 3
GW_OWNER = 4

# getWindow results
GW_RESULT_NOT_FOUND = 0
"""
When GetWindow returns 0, it means the window has not been found.
For example the last window has been reached, or an error has occurred.
"""

# SetLayeredWindowAttributes
LWA_ALPHA = 2
LWA_COLORKEY = 1
# Window messages
WM_NULL = 0
WM_COPYDATA = 74
WM_NOTIFY = 78
WM_USER = 1024
WM_QUIT = 18
WM_DEVICECHANGE = 537
WM_DISPLAYCHANGE = 0x7E
WM_GETTEXT = 13
WM_GETTEXTLENGTH = 14
WM_DESTROY = 2
WM_PAINT = 0x000F
WM_SHOWWINDOW = 24
WM_TIMER = 0x0113
WM_GETOBJECT = 0x003D
# Edit control window messages
EM_GETSEL = 176
EM_SETSEL = 177
EM_SCROLLCARET = 0xB7
EM_GETLINE = 196
EM_GETLINECOUNT = 186
EM_LINEFROMCHAR = 201
EM_LINEINDEX = 187
EM_LINELENGTH = 193
EM_POSFROMCHAR = 214
EM_CHARFROMPOS = 215
EM_GETFIRSTVISIBLELINE = 0x0CE
# Clipboard formats
CF_TEXT = 1
# mapVirtualKey constants
MAPVK_VK_TO_CHAR = 2
MAPVK_VSC_TO_VK_EX = 3
# Virtual key codes
VK_LBUTTON = 1
VK_RBUTTON = 2
VK_CANCEL = 3
VK_MBUTTON = 4
VK_XBUTTON1 = 5
VK_XBUTTON2 = 6
VK_BACK = 8
VK_TAB = 9
VK_CLEAR = 12
VK_RETURN = 13
VK_SHIFT = 16
VK_CONTROL = 17
VK_MENU = 18
VK_PAUSE = 19
VK_CAPITAL = 20
VK_FINAL = 0x18
VK_ESCAPE = 0x1B
VK_CONVERT = 0x1C
VK_NONCONVERT = 0x1D
VK_ACCEPT = 0x1E
VK_MODECHANGE = 0x1F
VK_SPACE = 32
VK_PRIOR = 33
VK_NEXT = 34
VK_END = 35
VK_HOME = 36
VK_LEFT = 37
VK_UP = 38
VK_RIGHT = 39
VK_DOWN = 40
VK_SELECT = 41
VK_PRINT = 42
VK_EXECUTE = 43
VK_SNAPSHOT = 44
VK_INSERT = 45
VK_DELETE = 46
VK_HELP = 47
VK_LWIN = 0x5B
VK_RWIN = 0x5C
VK_APPS = 0x5D
VK_SLEEP = 0x5F
VK_NUMPAD0 = 0x60
VK_NUMPAD1 = 0x61
VK_NUMPAD2 = 0x62
VK_NUMPAD3 = 0x63
VK_NUMPAD4 = 0x64
VK_NUMPAD5 = 0x65
VK_NUMPAD6 = 0x66
VK_NUMPAD7 = 0x67
VK_NUMPAD8 = 0x68
VK_NUMPAD9 = 0x69
VK_MULTIPLY = 0x6A
VK_ADD = 0x6B
VK_SEPARATOR = 0x6C
VK_SUBTRACT = 0x6D
VK_DECIMAL = 0x6E
VK_DIVIDE = 0x6F
VK_F1 = 0x70
VK_F2 = 0x71
VK_F3 = 0x72
VK_F4 = 0x73
VK_F5 = 0x74
VK_F6 = 0x75
VK_F7 = 0x76
VK_F8 = 0x77
VK_F9 = 0x78
VK_F10 = 0x79
VK_F11 = 0x7A
VK_F12 = 0x7B
VK_F13 = 0x7C
VK_F14 = 0x7D
VK_F15 = 0x7E
VK_F16 = 0x7F
VK_F17 = 0x80
VK_F18 = 0x81
VK_F19 = 0x82
VK_F20 = 0x83
VK_F21 = 0x84
VK_F22 = 0x85
VK_F23 = 0x86
VK_F24 = 0x87
VK_NUMLOCK = 0x90
VK_SCROLL = 0x91
VK_LSHIFT = 0xA0
VK_RSHIFT = 0xA1
VK_LCONTROL = 0xA2
VK_RCONTROL = 0xA3
VK_LMENU = 0xA4
VK_RMENU = 0xA5
VK_VOLUME_MUTE = 0xAD
VK_VOLUME_DOWN = 0xAE
VK_VOLUME_UP = 0xAF
VK_NONE = 0xFF

# Windows hooks
WH_KEYBOARD = 2
WH_MOUSE = 7
# win events
EVENT_SYSTEM_SOUND = 0x1
EVENT_SYSTEM_ALERT = 0x2
EVENT_SYSTEM_FOREGROUND = 0x3
EVENT_SYSTEM_MENUSTART = 0x4
EVENT_SYSTEM_MENUEND = 0x5
EVENT_SYSTEM_MENUPOPUPSTART = 0x6
EVENT_SYSTEM_MENUPOPUPEND = 0x7
EVENT_SYSTEM_CAPTURESTART = 0x8
EVENT_SYSTEM_CAPTUREEND = 0x9
EVENT_SYSTEM_MOVESIZESTART = 0xA
EVENT_SYSTEM_MOVESIZEEND = 0xB
EVENT_SYSTEM_CONTEXTHELPSTART = 0xC
EVENT_SYSTEM_CONTEXTHELPEND = 0xD
EVENT_SYSTEM_DRAGDROPSTART = 0xE
EVENT_SYSTEM_DRAGDROPEND = 0xF
EVENT_SYSTEM_DIALOGSTART = 0x10
EVENT_SYSTEM_DIALOGEND = 0x11
EVENT_SYSTEM_SCROLLINGSTART = 0x12
EVENT_SYSTEM_SCROLLINGEND = 0x13
EVENT_SYSTEM_SWITCHSTART = 0x14
EVENT_SYSTEM_SWITCHEND = 0x15
EVENT_SYSTEM_MINIMIZESTART = 0x16
EVENT_SYSTEM_MINIMIZEEND = 0x17
EVENT_OBJECT_CREATE = 0x8000
EVENT_OBJECT_DESTROY = 0x8001
EVENT_OBJECT_SHOW = 0x8002
EVENT_OBJECT_HIDE = 0x8003
EVENT_OBJECT_REORDER = 0x8004
EVENT_OBJECT_FOCUS = 0x8005
EVENT_OBJECT_SELECTION = 0x8006
EVENT_OBJECT_SELECTIONADD = 0x8007
EVENT_OBJECT_SELECTIONREMOVE = 0x8008
EVENT_OBJECT_SELECTIONWITHIN = 0x8009
EVENT_OBJECT_STATECHANGE = 0x800A
EVENT_OBJECT_LOCATIONCHANGE = 0x800B
EVENT_OBJECT_NAMECHANGE = 0x800C
EVENT_OBJECT_DESCRIPTIONCHANGE = 0x800D
EVENT_OBJECT_VALUECHANGE = 0x800E
EVENT_OBJECT_PARENTCHANGE = 0x800F
EVENT_OBJECT_HELPCHANGE = 0x8010
EVENT_OBJECT_DEFACTIONCHANGE = 0x8011
EVENT_OBJECT_ACCELERATORCHANGE = 0x8012
EVENT_OBJECT_LIVEREGIONCHANGED = 0x8019
EVENT_SYSTEM_DESKTOPSWITCH = 0x20
EVENT_OBJECT_INVOKED = 0x8013
EVENT_OBJECT_TEXTSELECTIONCHANGED = 0x8014
EVENT_OBJECT_CONTENTSCROLLED = 0x8015

EVENT_CONSOLE_CARET = 0x4001
EVENT_CONSOLE_UPDATE_REGION = 0x4002
EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003
EVENT_CONSOLE_UPDATE_SCROLL = 0x4004
EVENT_CONSOLE_LAYOUT = 0x4005
EVENT_CONSOLE_START_APPLICATION = 0x4006
EVENT_CONSOLE_END_APPLICATION = 0x4007
# IAccessible Child IDs
CHILDID_SELF = 0
# IAccessible Object IDs
OBJID_WINDOW = 0
OBJID_SYSMENU = -1
OBJID_TITLEBAR = -2
OBJID_MENU = -3
OBJID_CLIENT = -4
OBJID_VSCROLL = -5
OBJID_HSCROLL = -6
OBJID_SIZEGRIP = -7
OBJID_CARET = -8
OBJID_CURSOR = -9
OBJID_ALERT = -10
OBJID_SOUND = -11
OBJID_NATIVEOM = -16

# ShowWindow() commands
SW_HIDE = 0
SW_SHOWNORMAL = 1
SW_SHOWNA = 8

# SetWindowPos window constants
HWND_TOPMOST = HWND(-1)

# window sizing and positioning flags
SWP_NOACTIVATE = 0x0010
SWP_NOMOVE = 0x0002
SWP_NOSIZE = 0x0001
SWP_SHOWWINDOW = 0x0040

# RedrawWindow() flags
RDW_INVALIDATE = 0x0001
RDW_UPDATENOW = 0x0100
# MsgWaitForMultipleObjectsEx
QS_ALLINPUT = 0x04FF
MWMO_ALERTABLE = 0x0002


class MSGFLT(enum.IntEnum):
	# Actions associated with ChangeWindowMessageFilterEx
	# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex
	# Adds the message to the filter. This has the effect of allowing the message to be received.
	ALLOW = 1
	# Removes the message from the filter. This has the effect of blocking the message.
	DISALLOW = 2
	# Resets the window message filter to the default.
	# Any message allowed globally or process-wide will get through.
	RESET = 0


def setSystemScreenReaderFlag(val):
	_user32.SystemParametersInfo(SPI_SETSCREENREADER, val, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE)


def getSystemScreenReaderFlag():
	val = BOOL()
	_user32.SystemParametersInfo(SPI_GETSCREENREADER, 0, byref(val), 0)
	return bool(val.value)


def LOBYTE(word):
	return word & 0xFF


def HIBYTE(word):
	return word >> 8


def MAKEWORD(lo, hi):
	return (hi << 8) + lo


def LOWORD(long):
	return long & 0xFFFF


def HIWORD(long):
	return long >> 16


def GET_X_LPARAM(lp):
	return c_short(LOWORD(lp)).value


def GET_Y_LPARAM(lp):
	return c_short(HIWORD(lp)).value


def MAKELONG(lo, hi):
	return (hi << 16) + lo


def waitMessage():
	return _user32.WaitMessage()


def getMessage(*args) -> int:
	return winBindings.user32.GetMessage(*args)


def translateMessage(*args):
	return _user32.TranslateMessage(*args)


def dispatchMessage(*args):
	return _user32.DispatchMessage(*args)


def peekMessage(*args):
	try:
		res = _user32.PeekMessage(*args)
	except:  # noqa: E722
		res = 0
	return res


def registerWindowMessage(name):
	return _user32.RegisterWindowMessage(name)


def getAsyncKeyState(v):
	return _user32.GetAsyncKeyState(v)


def getKeyState(v):
	return _user32.GetKeyState(v)


def isWindow(hwnd):
	return _user32.IsWindow(hwnd)


def isDescendantWindow(parentHwnd, childHwnd):
	if (parentHwnd == childHwnd) or _user32.IsChild(parentHwnd, childHwnd):
		return True
	else:
		return False


def getForegroundWindow() -> HWNDVal:
	hwnd = _user32.GetForegroundWindow()
	return hwnd or 0


def setForegroundWindow(hwnd):
	_user32.SetForegroundWindow(hwnd)


def setFocus(hwnd):
	_user32.SetFocus(hwnd)


def getDesktopWindow() -> HWNDVal:
	hwnd = _user32.GetDesktopWindow()
	return hwnd or 0


def getControlID(hwnd):
	return _user32.GetWindowLong(hwnd, GWL_ID)


def getClientRect(hwnd):
	r = RECT()
	if not _user32.GetClientRect(hwnd, byref(r)):
		raise WinError()
	return r


def setWinEventHook(*args):
	return _user32.SetWinEventHook(*args)


def unhookWinEvent(*args):
	return _user32.UnhookWinEvent(*args)


def sendMessage(hwnd, msg, param1, param2):
	return _user32.SendMessage(hwnd, msg, param1, param2)


def getWindowThreadProcessID(hwnd: HWNDVal) -> tuple[int, int]:
	"""Returns a tuple of (processID, threadID)"""
	processID = DWORD()
	threadID = _user32.GetWindowThreadProcessId(hwnd, byref(processID))
	return (processID.value, threadID)


def getClassName(window: HWNDVal) -> str:
	buf = create_unicode_buffer(256)
	_user32.GetClassName(window, buf, 255)
	return buf.value


def keybd_event(*args):
	return _user32.keybd_event(*args)


def mouse_event(*args):
	# #8452: This is just a wrapper for the user32 mouse_event function,
	# i.e. events generated by calling this wrapper aren't ignored by NVDA.
	# To generate mouse events that are ignored by NVDA,
	# call L{mouseHandler.executeMouseEvent} instead.
	return _user32.mouse_event(*args)


def getAncestor(hwnd: HWNDVal, flags: int) -> HWNDVal:
	hwnd = _user32.GetAncestor(hwnd, flags)
	return hwnd or 0


def setCursorPos(x, y):
	_user32.SetPhysicalCursorPos(x, y)


def getCursorPos():
	point = POINT()
	_user32.GetPhysicalCursorPos(byref(point))
	return [point.x, point.y]


def getCaretPos():
	point = POINT()
	_user32.GetCaretPos(byref(point))
	return [point.x, point.y]


def getTopWindow(hwnd: HWNDVal) -> HWNDVal:
	hwnd = _user32.GetTopWindow(hwnd)
	return hwnd or 0


def getWindowText(hwnd):
	buf = create_unicode_buffer(1024)
	_user32.InternalGetWindowText(hwnd, buf, 1023)
	return buf.value


def getWindow(window: HWNDVal, relation: int) -> HWNDVal:
	hwnd = _user32.GetWindow(window, relation)
	return hwnd or 0


def isWindowVisible(window):
	return bool(_user32.IsWindowVisible(window))


def isWindowEnabled(window):
	return bool(_user32.IsWindowEnabled(window))


def getGUIThreadInfo(threadID):
	info = _GUITHREADINFO(cbSize=sizeof(_GUITHREADINFO))
	_user32.GetGUIThreadInfo(threadID, byref(info))
	return info


def getWindowStyle(hwnd):
	return _user32.GetWindowLong(hwnd, GWL_STYLE)


def getExtendedWindowStyle(hwnd):
	return _user32.GetWindowLong(hwnd, GWL_EXSTYLE)


def setExtendedWindowStyle(hwnd, exstyle):
	return _user32.SetWindowLong(hwnd, GWL_EXSTYLE, exstyle)


def SetLayeredWindowAttributes(hwnd, key, alpha, flags):
	return _user32.SetLayeredWindowAttributes(hwnd, key, alpha, flags)


def getPreviousWindow(hwnd: HWNDVal) -> HWNDVal:
	try:
		hwnd = _user32.GetWindow(hwnd, GW_HWNDPREV)
	except WindowsError:
		return 0
	return hwnd or 0


def getKeyboardLayout(idThread=0):
	return _user32.GetKeyboardLayout(idThread)


def RedrawWindow(hwnd, rcUpdate, rgnUpdate, flags):
	return _user32.RedrawWindow(hwnd, byref(rcUpdate), rgnUpdate, flags)


def getKeyNameText(scanCode, extended):
	buf = create_unicode_buffer(32)
	_user32.GetKeyNameText((scanCode << 16) | (extended << 24), buf, 31)
	return buf.value


def FindWindow(className, windowName):
	res = _user32.FindWindow(className, windowName)
	if res == 0:
		raise WinError()
	return res


MB_OK = 0
MB_RETRYCANCEL = 5
MB_ICONERROR = 0x10
MB_SYSTEMMODAL = 0x1000
IDRETRY = 4
IDCANCEL = 3


def MessageBox(hwnd, text, caption, type):
	res = _user32.MessageBox(hwnd, text, caption, type)
	if res == 0:
		raise WinError()
	return res


def PostMessage(hwnd, msg, wParam, lParam):
	if not _user32.PostMessage(hwnd, msg, wParam, lParam):
		raise WinError()


def VkKeyScanEx(ch, hkl):
	res = _user32.VkKeyScanEx(WCHAR(ch), hkl)
	if res == -1:
		raise LookupError
	return res >> 8, res & 0xFF


def ScreenToClient(hwnd, x, y):
	point = POINT(x, y)
	_user32.ScreenToClient(hwnd, byref(point))
	return point.x, point.y


def ClientToScreen(hwnd, x, y):
	point = POINT(x, y)
	_user32.ClientToScreen(hwnd, byref(point))
	return point.x, point.y


def NotifyWinEvent(event, hwnd, idObject, idChild):
	_user32.NotifyWinEvent(event, hwnd, idObject, idChild)


class STICKYKEYS(Structure):
	_fields_ = (
		("cbSize", DWORD),
		("dwFlags", DWORD),
	)

	def __init__(self, **kwargs):
		super(STICKYKEYS, self).__init__(cbSize=sizeof(self), **kwargs)


SKF_STICKYKEYSON = 0x00000001
SKF_AUDIBLEFEEDBACK = 0x00000040
SKF_TRISTATE = 0x00000080
SKF_TWOKEYSOFF = 0x00000100


def getSystemStickyKeys():
	sk = STICKYKEYS()
	_user32.SystemParametersInfo(SPI_GETSTICKYKEYS, 0, byref(sk), 0)
	return sk


def SendInput(inputs):
	n = len(inputs)
	arr = (INPUT * n)(*inputs)
	_user32.SendInput(n, arr, sizeof(INPUT))


@contextlib.contextmanager
def paint(hwnd: int, paintStruct: _PAINTSTRUCT | None = None):
	"""
	Context manager that wraps BeginPaint and EndPaint.
	:param painStruct: The paint structure used in the call to BeginPaint.
		if None (default), an empty structure is provided.
	"""
	if paintStruct is None:
		paintStruct = _PAINTSTRUCT()
	elif not isinstance(paintStruct, _PAINTSTRUCT):
		raise TypeError("Provided paintStruct is not of type PAINTSTRUCT")
	hdc = winBindings.user32.BeginPaint(hwnd, byref(paintStruct))
	if hdc == 0:
		raise WinError()
	try:
		yield hdc
	finally:
		winBindings.user32.EndPaint(hwnd, byref(paintStruct))


class WinTimer(object):
	"""Object that wraps the SetTimer function in user32.
	The timer is automatically destroyed using KillTimer when the object is terminated using L{terminate}.
	"""

	def __init__(self, hwnd, idEvent, elapse, timerFunc=None):
		"""Constructor

		See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-settimer
		for a description of the parameters.
		"""
		self.hwnd = hwnd
		self.idEvent = idEvent
		self.elapse = elapse
		# ensure timerFunc is a TIMERPROC, or is converted to a TIMERPROC,
		# and ensuring that None is handled as the correctly typed null function pointer.
		if isinstance(timerFunc, winBindings.user32.TIMERPROC):
			self.timerFunc = timerFunc
		elif timerFunc is None:
			self.timerFunc = winBindings.user32.TIMERPROC(0)
		else:
			self.timerFunc = winBindings.user32.TIMERPROC(timerFunc)
		self.ident = _user32.SetTimer(hwnd, idEvent, elapse, self.timerFunc)
		if self.ident == 0:
			raise WinError()
		if not hwnd:
			# If the window handle passed to SetTimer is valid
			# we need to keep the original idEvent to terminate the timer.
			# If no hwnd is given, we need to pass l{ident} when killing it.
			self.idEvent = self.ident

	def terminate(self):
		"""Terminates the timer.
		This should be called from the thread that initiated the timer.
		"""
		if not _user32.KillTimer(self.hwnd, self.idEvent):
			raise WinError()


# Windows Clipboard format constants
CF_UNICODETEXT = 13


@contextlib.contextmanager
def openClipboard(hwndOwner=None):
	"""
	A context manager version of OpenClipboard from user32.
	Use as the expression of a 'with' statement, and CloseClipboard will automatically be called at the end.
	"""
	if not winBindings.user32.OpenClipboard(hwndOwner):
		raise ctypes.WinError()
	try:
		yield
	finally:
		winBindings.user32.CloseClipboard()


def emptyClipboard():
	if not _user32.EmptyClipboard():
		raise ctypes.WinError()


def getClipboardData(format):
	# We only support unicode text for now
	if format != CF_UNICODETEXT:
		raise ValueError("Unsupported format")
	# Fetch the data from the clipboard as a global memory handle
	h = winBindings.user32.GetClipboardData(format)
	if not h:
		raise ctypes.WinError()
	# Lock the global memory  while we fetch the unicode string
	# But make sure not to free the memory accidentally -- it is not ours
	h = winKernel.HGLOBAL(h, autoFree=False)
	with h.lock() as addr:
		# Read the string from the local memory address
		return wstring_at(addr)


def setClipboardData(format, data):
	# For now only unicode is a supported format
	if format != CF_UNICODETEXT:
		raise ValueError("Unsupported format")
	text = data
	bufLen = len(text.encode(WCHAR_ENCODING, errors="surrogatepass")) + 2
	# Allocate global memory
	h = winKernel.HGLOBAL.alloc(winKernel.GMEM_MOVEABLE, bufLen)
	# Acquire a lock to the global memory receiving a local memory address
	with h.lock() as addr:
		# Write the text into the allocated memory
		buf = (c_wchar * bufLen).from_address(addr)
		buf.value = text
	# Set the clipboard data with the global memory
	if not winBindings.user32.SetClipboardData(format, h):
		raise ctypes.WinError()
	# NULL the global memory handle so that it is not freed at the end of scope as the clipboard now has it.
	h.forget()


def findTopLevelWindow(predicate: Callable[[int], bool]) -> int | None:
	"""
	Find the first top-level window that matches the given predicate.

	:param predicate: A callable that takes a window handle (hWnd) as an integer
		and returns True if the window matches the search criteria, False otherwise
	:returns: The handle of the first matching top-level window, or None if no
		matching window is found
	:raises ctypes.WinError: If the Windows API call fails
	"""
	found = None

	@_WNDENUMPROC
	def enumWindowsProc(hWnd: int, _lParam: int) -> int:
		"""
		Callback function for enumerating windows.

		This function is called for each top-level window on the screen during enumeration.
		It checks if the window matches the predicate criteria and stops enumeration when found.

		:param hWnd: Handle to the window being enumerated
		:param _lParam: Application-defined value passed to the enumeration function (unused)
		:return: 0 to stop enumeration when window is found, 1 to continue enumeration
		"""
		if predicate(hWnd):
			nonlocal found
			found = hWnd
			return 0
		return 1

	if not winBindings.user32.EnumWindows(enumWindowsProc, 0) and (error := ctypes.get_last_error()):
		raise ctypes.WinError(error)
	return found
